/*
 *  Copyright (C) 2012 Компания ВИТ. web: http://diasled.ru Е-mail:  vit@diasled.ru
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package ru.apertum.diasled.cfg;

import ru.apertum.diasled.cfg.ColorScale;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;
import ru.apertum.diasled.common.Uses;
import ru.apertum.diasled.redactor.SensorDraw;
import ru.vit.cfgreader.ContourPoint;
import ru.vit.cfgreader.Insoles;
import ru.vit.cfgreader.Sensor;
//import ru.apertum.diasled.forms.FRedactor.Outline;

/**
 * Класс содержащий все параметры:
 * - контуры
 * - датчики
 * - коэффициенты
 * 
 * @author Evgeniy Egorov
 */
public class Configuration {

    public Configuration() {
    }
    public int min_x = 100000;
    public int max_x = -100000;
    public int min_y = 100000;
    public int max_y = -100000;
    public int center_x = 0;
    public int center_y = 0;
    public int center_x_b = 0;
    public int center_y_b = 0;
    // сначала х потом у
    private final HashMap<Integer, HashMap<Integer, SensorDraw>> finder = new HashMap<>();
    private final SensorDraw nullSens = new SensorDraw();
    public final static int NOT_SENS = -7896541;

    public SensorDraw findSensor(int x, int y) {
        if (finder.get(x) == null || finder.get(x).get(y) == null) {
            return nullSens;
        }
        return finder.get(x).get(y);
    }
    /**
     * Чтобы высоты уравновесить с размером всей картинки.
     */
    private int coefReductionZ = 1;

    public int getCoefReductionZ() {
        return coefReductionZ;
    }

    public void setCoefReductionZ(int coefReductionZ) {
        this.coefReductionZ = coefReductionZ;
    }

    private void detectBorder() {
        for (Outline outline : getOutlines()) {
            for (MyPoint point : outline.getRoute()) {
                if (min_x > point.x) {
                    min_x = point.x;
                }
                if (max_x < point.x) {
                    max_x = point.x;
                }
                if (min_y > point.y) {
                    min_y = point.y;
                }
                if (max_y < point.y) {
                    max_y = point.y;
                }
            }
        }
        nullSens.setValue(NOT_SENS);
        finder.clear();
        center_x_b = 0;
        center_y_b = 0;
        for (SensorDraw sensor : getSensors()) {
            final HashMap mm = finder.get(sensor.x);
            if (mm == null) {
                finder.put(sensor.x, new HashMap<Integer, SensorDraw>());
            }
            finder.get(sensor.x).put(sensor.y, sensor);
            if (min_x > sensor.x) {
                min_x = sensor.x;
            }
            if (max_x < sensor.x) {
                max_x = sensor.x;
            }
            if (min_y > sensor.y) {
                min_y = sensor.y;
            }
            if (max_y < sensor.y) {
                max_y = sensor.y;
            }
            center_x_b = center_x_b + sensor.x;
            center_y_b = center_y_b + sensor.y;
        }
        center_x = (min_x + max_x) / 2;
        center_y = (min_y + max_y) / 2;
        center_x_b = center_x_b / getSensors().size();
        center_y_b = center_y_b / getSensors().size();

        // Отсортируем все сенсоры по контурам
        for (SensorDraw sensor : getSensors()) {
            double min = 1000000000;
            Outline route = null;
            for (Outline outline : getOutlines()) {
                // рисуем ломаную
                MyPoint first = null;
                for (MyPoint point : outline.getRoute()) {

                    if (first == null) {
                        first = point;
                    } else {
                        double d = Uses.distancePointToSegment(sensor, first, point);
                        if (d < min) {
                            min = d;
                            route = outline;
                        }
                        first = point;
                    }
                }
            }
            sensor.setOutline(route);
        }
    }
    /**
     * Сенсоры
     */
    @Expose
    @SerializedName("sensors")
    private LinkedList<SensorDraw> sensors = new LinkedList<>();

    public LinkedList<SensorDraw> getSensors() {
        return sensors;
    }

    public void setSensors(LinkedList<SensorDraw> sensors) {
        this.sensors = sensors;
    }
    /**
     * Контуры
     */
    @Expose
    @SerializedName("outlines")
    private LinkedList<Outline> outlines = new LinkedList<>();

    public LinkedList<Outline> getOutlines() {
        return outlines;
    }

    public void setOutlines(LinkedList<Outline> outlines) {
        this.outlines = outlines;
    }
    /**
     * Коэффициент пересчета веса
     * @param file
     * @throws IOException 
     */
    @Expose
    @SerializedName("weight_conversion_coeff")
    private double weightConversionCoeff = 1;

    public double getWeightConversionCoeff() {
        return weightConversionCoeff;
    }

    public void setWeightConversionCoeff(double weightConversionCoeff) {
        this.weightConversionCoeff = weightConversionCoeff;
    }
    /**
     * коэффициенты смещения и уселения
     */
    @Expose
    @SerializedName("coefs")
    private LinkedList<Short> coefs = new LinkedList<>();

    public LinkedList<Short> getCoefs() {
        return coefs;
    }

    public void setCoefs(LinkedList<Short> coefs) {
        this.coefs = coefs;
    }
    /**
     * Кодовое имя системы
     */
    @Expose
    @SerializedName("system_name")
    private String systemName;

    public String getSystemName() {
        return systemName;
    }

    public void setSystemName(String systemName) {
        this.systemName = systemName;
    }
    /**
     * Настройки цветовой шкалы
     */
    @Expose
    @SerializedName("color_scale")
    private ColorScale colorScale = new ColorScale();

    public ColorScale getColorScale() {
        return colorScale;
    }

    public void setColorScale(ColorScale colorScale) {
        this.colorScale = colorScale;
    }
    /**
     * это тот самый номерок который приехал из железки и по которому нашли файлик настроек
     */
    private int sysNum = 0;

    public int getSysNum() {
        return sysNum;
    }

    public void setSysNum(int sysNum) {
        this.sysNum = sysNum;
    }

    /**
     * Сохранение всей конфигурации в файл *.vit
     * @param file файл для сохранения
     * @throws IOException при записи в файл
     */
    public void save(File file) throws IOException {
        final FileOutputStream fos;
        try {
            fos = new FileOutputStream(file);
        } catch (FileNotFoundException ex) {
            throw new RuntimeException(ex);
        }
        fos.write(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(this).getBytes("utf-8"));
        fos.flush();
        fos.close();
    }
    static final private HashMap<String, Configuration> CFGS_st = new HashMap<>();
    static final private HashMap<Integer, Configuration> CFGS_int = new HashMap<>();

    /**
     * Загрузка конфигурации из файла.
     * @param file загружаемый файл
     * @return класс загруженной конфигурации
     * @throws IOException при чтении из файла
     */
    public static Configuration load(File file) {
        if (CFGS_st.get(file.getAbsolutePath()) != null) {
            return CFGS_st.get(file.getAbsolutePath());
        }
        final StringBuilder sb = new StringBuilder();
        try (Scanner sc = new Scanner(file, "utf-8")) {
            while (sc.hasNext()) {
                sb.append(sc.next());
            }
        } catch (FileNotFoundException ex) {
            System.err.println(ex);
        }
        final Configuration res = new GsonBuilder().create().fromJson(sb.toString(), Configuration.class);
        res.detectBorder();
        sb.setLength(0);
        CFGS_st.put(file.getAbsolutePath(), res);
        return res;
    }

    public static Configuration loadByID(final int id) {
        if (CFGS_int.get(id) != null) {
            return CFGS_int.get(id);
        }
        // Загрузим конфигурацию
        final String[] files = new File("config").list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith("" + id + ".vit") || name.endsWith("" + id + ".VIT");
            }
        });
        if (files.length == 0) {
            return null;
        }
        final Configuration cfg = Configuration.load(new File("config/" + files[0]));
        cfg.setSysNum(id);
        CFGS_int.put(id, cfg);
        return cfg;
    }

    /**
     * Загрузка из старого формата
     * @param cfg
     * @return 
     */
    public static Configuration cfg(Insoles cfg, String name) {
        final Configuration conf = new Configuration();
        conf.setSystemName(name);
        conf.setWeightConversionCoeff(cfg.getWeightCoeff());
        conf.setWeightConversionCoeff(cfg.getWeightCoeff());
        for (Short s : cfg.getCoeff()) {
            conf.getCoefs().add(s);
        }

        if (cfg.getLeftContour() != null && !cfg.getLeftContour().isEmpty()) {
            final Outline outline = new Outline();
            for (ContourPoint pnt : cfg.getLeftContour()) {
                outline.getRoute().add(new MyPoint((int) (pnt.getX() * 3.5), (int) (pnt.getY() * 3.5)));
            }
            conf.getOutlines().add(outline);
        }
        if (cfg.getRightContour() != null && !cfg.getRightContour().isEmpty()) {
            final Outline outline = new Outline();
            for (ContourPoint pnt : cfg.getRightContour()) {
                outline.getRoute().add(new MyPoint((int) (pnt.getX() * 3.5), (int) (pnt.getY() * 3.5)));
            }
            conf.getOutlines().add(outline);
        }

        if (!cfg.getSensors().isEmpty()) {
            for (Sensor snr : cfg.getSensors()) {
                final SensorDraw sensor = new SensorDraw((int) (snr.getX() * 3.5), (int) (snr.getY() * 3.5));
                // не требуется физический номер в новом конфиге sensor.setPhisicalNum(snr.getNumber() - 1);
                sensor.setLogicalNum(snr.getId() - 1);
                for (int i : snr.getTarData()) {
                    sensor.getTarData().add(i);
                }
                conf.getSensors().add(sensor);
            }
        }
        conf.detectBorder();
        return conf;
    }
}
